package com.muyi.spring;

import com.muyi.spring.annotation.*;
import com.muyi.spring.beans.BeanDefinition;
import com.muyi.spring.beans.BeanNameAware;
import com.muyi.spring.beans.BeanPostProcessor;
import com.muyi.spring.beans.InitializingBean;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;

public class AnnotationConfigApplicationContext {

    private Class configClass;
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap();
    private Map<String, Object> singletonObjects = new HashMap();
    private List<BeanPostProcessor> postProcessors = new ArrayList();

    public AnnotationConfigApplicationContext(Class componentClass) {
        this.configClass = componentClass;
        scan(componentClass);

        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            BeanDefinition beanDefinition = entry.getValue();
            if ("singleton".equals(beanDefinition.getScope()) && !beanDefinition.isLazy()) {
                Object bean = createBean(entry.getKey(), beanDefinition);
                singletonObjects.put(entry.getKey(), bean);
            }
        }
    }

    public Object getBean(String beanName) {

        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException();
        }

        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        if (beanDefinition.getScope().equals("singleton")) {
            Object singletonBean = singletonObjects.get(beanName);
            if (singletonBean == null) {
                singletonBean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, singletonBean);
            }
            return singletonBean;
        } else {
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }


    private Object createBean(String key, BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getClazz();
        Object bean = null;
        try {

            bean = clazz.getConstructor().newInstance();
            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    field.setAccessible(true);
                    field.set(bean, getBean(field.getName()));
                }
            };

            if (BeanNameAware.class.isAssignableFrom(clazz)) {
                ((BeanNameAware)bean).setBeanName(key);
            }

            for (BeanPostProcessor postProcessor : postProcessors) {
                bean = postProcessor.postProcessBeforeInitialization(bean, key);
            }

            if (InitializingBean.class.isAssignableFrom(clazz)) {
                ((InitializingBean)bean).afterPropertiesSet();
            }

            for (BeanPostProcessor postProcessor : postProcessors) {
                bean = postProcessor.postProcessAfterInitialization(bean, key);
            }

        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        
        return bean;
    }


    public void scan(Class componentClass) {

        ComponentScan annotation = (ComponentScan) componentClass.getAnnotation(ComponentScan.class);
        String scanPath = annotation.basePackage();

        scanPath = scanPath.replace('.','/');
        ClassLoader classLoader = AnnotationConfigApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(scanPath);
        if (resource==null) {
            return;
        }
        File file = new File(resource.getFile());

        if (file.isDirectory()) {

            for (File f : file.listFiles()) {

                String absolutePath = f.getAbsolutePath();
                String path = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"))
                        .replace("/", ".");
                try {
                    Class<?> clazz = classLoader.loadClass(path);
                    if (clazz.isAnnotationPresent(Component.class)) {

                        if(BeanPostProcessor.class.isAssignableFrom(clazz)) {
                            BeanPostProcessor instance = (BeanPostProcessor) clazz.getConstructor().newInstance();
                            postProcessors.add(instance);
                        }

                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);
                        beanDefinition.setScope("singleton");
                        beanDefinition.setLazy(false);

                        Component anntation = clazz.getAnnotation(Component.class);
                        String beanName = anntation.name();
                        if ("".equals(beanName)) {
                            beanName = Introspector.decapitalize(clazz.getSimpleName());
                        }

                        if (clazz.isAnnotationPresent(Scope.class)) {
                            Scope scope = clazz.getAnnotation(Scope.class);
                            beanDefinition.setScope(scope.value());
                        }

                        if (clazz.isAnnotationPresent(Lazy.class)) {
                            beanDefinition.setLazy(true);
                        }

                        beanDefinitionMap.put(beanName, beanDefinition);
                    }

                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InvocationTargetException e) {
                    e.printStackTrace();
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
